/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Assembler for Emu10k1
 */
/*
 * Copyright (C) 4Front Technologies 1996-2008.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <sys/param.h>

#define	MAX_GPR	256
#define	MAX_GPR_PARMS	60
#define	MAX_CONST_PARMS	128
#define	GPR_NAME_SIZE 32

typedef struct {
	char name[GPR_NAME_SIZE];
	unsigned int num;
	int type;
	int def;
} gpr_t;

typedef struct {
	unsigned int gpr;
	unsigned int value;
} const_t;

typedef struct {
	unsigned int ngpr;

	gpr_t gpr[MAX_GPR_PARMS];
} gpr_info;

typedef struct {
	unsigned int nconst;

	const_t consts[MAX_CONST_PARMS];
} const_info;

typedef struct {
	unsigned int code[1024];
	gpr_info parms;
	const_info consts;
	int	ninit;
	struct {
		uint32_t	gpr;
		uint32_t	value;
		char		name[GPR_NAME_SIZE];
	} init[MAX_GPR];
} emu10k1_file;

#define	MAX_NAME	64
#define	MAX_SYMBOLS	1024

static int parms_only = 0;
static int is_audigy = 0;
static int verbose = 0;

static int gpr_base = 0x100;
static int input_base = 0x10;
static int output_base = 0x20;

static char *progname;

typedef struct {
	char name[MAX_NAME];
	int type;
#define	SY_DUMMY	0
#define	SY_GPR		1
#define	SY_INPUT	2
#define	SY_OUTPUT	3
#define	SY_CONST	4
#define	SY_FX		5
#define	SY_ACCUM	6
#define	SY_PARM		7
	int arg;
} sym_t;

typedef struct {
	char *name;
	int opcode;
} instruction_t;

static char remarks[2048] = "";
static char *banner =
	"/*\n"
	" * Note: This file was automatically generated by %s\n"
	" * on %s.\n"
	" */\n";

/*
 * Instructions.  Each instruction takes 4 arguments, R, A, X, and Y.
 */
static instruction_t instructions[] = {
	{ "MACS",	0x0},	/* R = A + (X * Y >> 31); saturation */
	{ "MACS1",	0x1},	/* R = A + (-X * Y >> 31); saturation */
	{ "MACW",	0x2},	/* R = A + (X * Y >> 31); wraparound */
	{ "MACW1",	0x3},	/* R = A + (-X * Y >> 31); wraparound */
	{ "MACINTS",	0x4},	/* R = A + (X * Y); saturation */
	{ "MACINTW",	0x5},	/* R = A + (X * Y); wraparound */
	{ "SUM",	0x6},	/* R = A + X + Y; saturation */
	{ "ACC3",	0x6},	/* R = A + X + Y; saturation */
	{ "MACMV",	0x7},	/* R = A, acc += X * Y >> 31 */
	{ "ANDXOR",	0x8},	/* R = (A & X) ^ Y */
	{ "TSTNEG",	0x9},	/* R = (A >= Y) ? X : ~X */
	{ "LIMIT",	0xa},	/* R = (A >= Y) ? X : Y */
	{ "LIMIT1",	0xb},	/* R = (A < Y) ? X : Y */
	{ "LOG",	0xc},	/* R = ... (log?) */
	{ "EXP",	0xd},	/* R = ... (exp?) */
	{ "INTERP",	0xe},	/* R = A + (X * (Y - A) >> 31) */
	{ "SKIP",	0xf},	/* R, CCR, CC_TEST, COUNT */
	{ NULL, 0}
};

#define	CHECK_COUNT(tokens, cnt, mincnt, maxcnt)			\
	if (cnt < mincnt) {						\
		error("Too few parameters for '%s' (have %d, min %d)",	\
		    tokens[0], cnt - 1, mincnt - 1);			\
		return;							\
	}								\
	if (cnt > maxcnt) {						\
		error("Too many parameters for '%s' (have %d, max %d)",	\
		    tokens[0], cnt - 1, maxcnt - 1);			\
		return;							\
	}

static sym_t symtab[MAX_SYMBOLS];
static int nsyms = 0;

static int lineno = 0, errors = 0;
static emu10k1_file fle;
static int pc;

static int ngpr = 0;
static char *infile;

static int
getaline(FILE *input, char **tokens)
{
	char *s, *ls;
	static char *stmt = NULL, *lasts = NULL;
	static char line[4096];
	int cnt, tokcnt;

	for (;;) {

		if (stmt == NULL) {
			if (fgets(line, sizeof (line), input) == NULL)
				return (-1);
			lineno++;

			/*
			 * Special handling for .' comments.  We use
			 * .' as a keyword to ensure that entire
			 * comment makes it through the C preprocessor
			 * unmolested.  We also need to make sure *we*
			 * don't molest it either.  The comment will
			 * be exported to any resulting header,
			 * allowing us to pass through copyright and
			 * other information from the source file to
			 * the resulting header.
			 */
			s = line;
			s += strspn(s, " \t");
			if ((strncmp(s, ".'", 2) == 0) &&
			    (strchr(" \t\n", s[2]) != NULL)) {
				/* chop off trailing new line */
				(void) strtok(line, "\n");
				tokens[0] = s;
				s += 2;
				s += strspn(s, " \t");
				if ((s[0] == '\'') &&
				    (s[strlen(s) - 1] == '\'')) {
					s[strlen(s) - 1] = 0;
					s++;
				}
				tokens[1] = s;
				tokens[0][2] = 0;
				tokens[2] = NULL;
				stmt = NULL;
				return (strlen(tokens[1]) ? 2 : 1);
			}

			/* strip off any C++ style comments that CPP missed */
			if ((s = strstr(line, "//")) != NULL) {
				*s = '\0';
			}
			stmt = strtok_r(line, ";\n", &lasts);
		} else {
			stmt = strtok_r(NULL, ";\n", &lasts);
		}

		if (stmt != NULL) {
			break;
		}
	}

	/*
	 * Ok, we have a statement, lets tokenize it.  For
	 * simplicities sake we convert "OPCODE(arg1, arg2)" into
	 * "OPCODE arg1 arg2".  This means that commas and parens are
	 * treated as whitespace.  This can lead to some really messed
	 * up syntaxes that get assembled properly (such as nested
	 * calls, empty arguments, etc.)  Hopefully people don't abuse
	 * this.
	 */
	ls = NULL;
	s = strtok_r(stmt, " \t\n(),", &ls);
	cnt = 0;
	tokcnt = 0;
	while (cnt < 10) {
		tokens[cnt++] = s;
		if (s != NULL) {
			tokcnt++;
			s = strtok_r(NULL, " \t\n(),", &ls);
		}
	}
	return (tokcnt);
}

static void
error(char *msg, ...)
{
	va_list va;
	char msgbuf[1024];

	va_start(va, msg);
	(void) vsnprintf(msgbuf, sizeof (msgbuf), msg, va);
	va_end(va);

	(void) fprintf(stderr, "Error: %s on line %d of %s\n", msgbuf, lineno,
	    infile);
	errors++;
}

static sym_t *
find_symbol(char *name)
{
	int i;

	for (i = 0; i < nsyms; i++)
		if (strcmp(symtab[i].name, name) == 0) {
			return (&symtab[i]);
		}

	return (NULL);
}

static void
add_symbol(char *name, int type, int arg)
{
	sym_t *sym;

	if (nsyms >= MAX_SYMBOLS) {
		error("Symbol table full");
		exit(-1);
	}

	if (find_symbol(name) != NULL) {
		error("Dublicate symbol '%s'", name);
		return;
	}

	if (strlen(name) >= MAX_NAME) {
		error("Symbol name '%s' too long", name);
		exit(-1);
	}

	sym = &symtab[nsyms++];

	(void) strcpy(sym->name, name);
	sym->type = type;
	sym->arg = arg;
}

static void
add_init(uint32_t gpr, uint32_t val, const char *name)
{
	int	n;

	n = fle.ninit;
	if (n >= MAX_GPR) {
		error("Too many GPRs");
		return;
	}
	fle.init[n].gpr = gpr;
	fle.init[n].value = val;
	if (name)
		(void) strlcpy(fle.init[n].name, name,
		    sizeof (fle.init[n].name));
	fle.ninit++;
}

static void
compile_gpr(char **tokens, int cnt)
{
	CHECK_COUNT(tokens, cnt, 2, 2);

	if (ngpr >= MAX_GPR)
		error("Too many GPR variables");

	add_symbol(tokens[1], SY_GPR, gpr_base + ngpr++);
}

static void
compile_rem(char **tokens, int cnt)
{
	int i;

	(void) strlcat(remarks, " *", sizeof (remarks));
	for (i = 1; i < cnt; i++) {
		(void) strlcat(remarks, " ", sizeof (remarks));
		(void) strlcat(remarks, tokens[i], sizeof (remarks));
	}
	(void) strlcat(remarks, "\n", sizeof (remarks));
}

static void
declare_const(unsigned int gpr, char *value)
{
	int n, intv;
	float v;

	n = fle.consts.nconst;

	if (n >= MAX_CONST_PARMS) {
		error("Too many constant parameters");
		return;
	}

	if (*value == 'I') {
		if (sscanf(&value[1], "%g", &v) != 1) {
			error("Bad floating point value (%s)", value);
			return;
		}
		intv = (int)v;
	} else if (*value == '0' && value[1] == 'x') {
		if (sscanf(&value[2], "%x", (unsigned *)&intv) != 1) {
			error("Bad hexadecimal value (%s)", value);
			return;
		}
	} else {
		if (sscanf(value, "%g", &v) != 1) {
			error("Bad floating point value (%s)", value);
			return;
		}
		intv = (int)(v * 0x7fffffff);
	}

	fle.consts.consts[n].gpr = gpr;
	fle.consts.consts[n].value = intv;
	fle.consts.nconst = n + 1;

	add_init(gpr, intv, NULL);
}

static void
compile_const(char **tokens, int cnt)
{
	CHECK_COUNT(tokens, cnt, 2, 3);
	char *name = tokens[1];
	char *value = tokens[2] ? tokens[2] : tokens[1];

	if (ngpr >= MAX_GPR)
		error("Too many GPR variables");

	declare_const(ngpr, value);

	add_symbol(name, SY_GPR, gpr_base + ngpr++);
}

static void
compile_bool(char **tokens, int cnt)
{
	char *parm, *def;
	int n, num;

	CHECK_COUNT(tokens, cnt, 3, 3);

	parm = tokens[1];
	def = tokens[2];

	n = fle.parms.ngpr;
	if (n >= MAX_GPR_PARMS) {
		error("Too many GPR parameters");
		return;
	}

	if (sscanf(def, "%d", &num) != 1) {
		error("Bad integer value near '%s'", def);
		return;
	}

	(void) strcpy(fle.parms.gpr[n].name, parm);
	fle.parms.gpr[n].num = ngpr;
	fle.parms.gpr[n].def = num;
	fle.parms.ngpr = n + 1;

	add_init(ngpr, num, parm);

	add_symbol(parm, SY_PARM, gpr_base + ngpr++);
}

static void
compile_mono(char **tokens, int cnt)
{
	char *parm, *def;
	int n, num;

	CHECK_COUNT(tokens, cnt, 3, 3);

	parm = tokens[1];
	def = tokens[2];

	n = fle.parms.ngpr;
	if (n >= MAX_GPR_PARMS) {
		error("Too many GPR parameters");
		return;
	}

	if (sscanf(def, "%d", &num) != 1) {
		error("Bad integer value near '%s'", def);
		return;
	}

	(void) strcpy(fle.parms.gpr[n].name, parm);
	fle.parms.gpr[n].num = ngpr;
	fle.parms.gpr[n].def = num;
	fle.parms.ngpr = n + 1;

	add_init(ngpr, num, parm);

	add_symbol(parm, SY_PARM, gpr_base + ngpr++);
}

static void
compile_stereo(char **tokens, int cnt)
{
	char *parm, *def;
	int n, num;
	char tmp[128];

	CHECK_COUNT(tokens, cnt, 3, 3);

	parm = tokens[1];
	def = tokens[2];

	n = fle.parms.ngpr;
	if (n >= MAX_GPR_PARMS) {
		error("Too many GPR parameters");
		return;
	}

	if (sscanf(def, "%d", &num) != 1) {
		error("Bad integer value near '%s'", def);
		return;
	}

	(void) strcpy(fle.parms.gpr[n].name, parm);
	fle.parms.gpr[n].num = ngpr;
	fle.parms.gpr[n].def = num | (num << 8);
	fle.parms.ngpr = n + 1;

	add_init(ngpr, num, parm);
	add_init(ngpr + 1, num, NULL);

	(void) sprintf(tmp, "%s_L", parm);
	add_symbol(tmp, SY_PARM, gpr_base + ngpr++);
	(void) sprintf(tmp, "%s_R", parm);
	add_symbol(tmp, SY_PARM, gpr_base + ngpr++);
}

static void
compile_input(char **tokens, int cnt)
{
	int num;

	CHECK_COUNT(tokens, cnt, 3, 3);

	if (sscanf(tokens[2], "%d", &num) != 1) {
		error("Bad integer value near '%s'", tokens[2]);
		return;
	}

	add_symbol(tokens[1], SY_INPUT, input_base + num);
}

static void
compile_send(char **tokens, int cnt)
{
	int num;

	CHECK_COUNT(tokens, cnt, 3, 3);

	if (sscanf(tokens[2], "%d", &num) != 1) {
		error("Bad integer near '%s'", tokens[2]);
		return;
	}

	add_symbol(tokens[1], SY_FX, num);
}

static void
compile_output(char **tokens, int cnt)
{
	int num;

	CHECK_COUNT(tokens, cnt, 3, 3);

	if (sscanf(tokens[2], "%d", &num) != 1) {
		error("Bad integer value near '%s'", tokens[2]);
		return;
	}

	add_symbol(tokens[1], SY_OUTPUT, output_base + num);
}

static void
compile_directive(char **tokens, int cnt)
{
	if (strcmp(tokens[0], ".gpr") == 0) {
		compile_gpr(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".const") == 0) {
		compile_const(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".stereo") == 0) {
		compile_stereo(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".mono") == 0) {
		compile_mono(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".bool") == 0) {
		compile_bool(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".input") == 0) {
		compile_input(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".send") == 0) {
		compile_send(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".output") == 0) {
		compile_output(tokens, cnt);
		return;
	}

	if (strcmp(tokens[0], ".rem") == 0) {
		compile_rem(tokens, cnt);
		return;
	}
	if (strcmp(tokens[0], ".'") == 0) {
		compile_rem(tokens, cnt);
		return;
	}

	error("Unknown directive '%s'", tokens[0]);
}

static void
compile_asm(char **tokens, int cnt)
{
	sym_t *symbols[4];
#define	EMIT(o, r, a, x, y) \
	fle.code[pc*2] =  ((x) << 10) | (y);			\
	fle.code[pc*2+1] = ((o) << 20) | ((r) << 10) | a; pc++
#define	EMIT_AUDIGY(o, r, a, x, y) \
	fle.code[pc*2] =  ((x) << 12) | (y);			\
	fle.code[pc*2+1] = ((o) << 24) | ((r) << 12) | a; pc++

	int i, nerr = 0;
	int ninputs = 0;

	CHECK_COUNT(tokens, cnt, 5, 5);

	for (i = 0; i < 4; i++) {
		if ((symbols[i] = find_symbol(tokens[i+1])) == NULL) {
			(void) fprintf(stderr, "%s\n", tokens[i+1]);
			nerr++;
			error("Undefined symbol '%s'", tokens[i + 1]);
			continue;
		}

		if (symbols[i]->type == SY_INPUT)
			ninputs++;

		if (symbols[i]->type == SY_ACCUM && i != 1)
			error("Bad usage of 'accum' operand.");
	}

	if (nerr > 0)
		return;

	if (ninputs > 1) {
		error("Attempt to access more than one input "
		    "GPRs by the same instruction");
	}

	for (i = 0; instructions[i].name != NULL; i++)
		if (strcasecmp(tokens[0], instructions[i].name) == 0)  {

			if (is_audigy) {
				EMIT_AUDIGY(instructions[i].opcode,
				    symbols[0]->arg,
				    symbols[1]->arg,
				    symbols[2]->arg,
				    symbols[3]->arg);
			} else {
				EMIT(instructions[i].opcode,
				    symbols[0]->arg,
				    symbols[1]->arg,
				    symbols[2]->arg,
				    symbols[3]->arg);
			}

			return;
		}

	error("Unrecognized instruction '%s'", tokens[0]);
}

static void
init_compiler(void)
{
	char tmp[100];
	int i;

	(void) memset(&fle, 0, sizeof (fle));
	/*
	 * Initialize few predefined GPR parameter registers. These
	 * definitions have to be in sync with the GPR_* macros in
	 * <sblive.h>.
	 */

	/*
	 * Make sure we start at gpr id 2 for now; 0 and 1 may be used
	 * differently.
	 */
	add_symbol("NULL", SY_DUMMY, gpr_base + ngpr++);
	add_symbol("NULL_", SY_DUMMY, gpr_base + ngpr++);

	pc = 0;

	if (is_audigy) {
		/* Initialize the code array with NOPs (AUDIGY) */
		for (i = 0; i < 512; i++) {
			fle.code[i * 2 + 0] = (0xc0 << 12) | 0xc0;
			fle.code[i * 2 + 1] =
			    (0x06 << 24) | (0xc0 << 12) | 0xc0;
		}

		for (i = 0; i < 32; i++) {
			(void) sprintf(tmp, "fx%d", i);
			add_symbol(tmp, SY_FX, i);
		}
	} else {
		/* Initialize the code array with NOPs (LIVE) */
		for (i = 0; i < 512; i++) {
			fle.code[i * 2 + 0] = 0x10040;
			fle.code[i * 2 + 1] = 0x610040;
		}

		for (i = 0; i < 16; i++) {
			(void) sprintf(tmp, "fx%d", i);
			add_symbol(tmp, SY_FX, i);
		}
	}

	/*
	 * Constants
	 */

	if (is_audigy) {
		/* Audigy symbols */
		add_symbol("0", SY_CONST, 0x0c0);
		add_symbol("1", SY_CONST, 0x0c1);
		add_symbol("2", SY_CONST, 0x0c2);
		add_symbol("3", SY_CONST, 0x0c3);
		add_symbol("4", SY_CONST, 0x0c4);
		add_symbol("8", SY_CONST, 0x0c5);
		add_symbol("16", SY_CONST, 0x0c6);
		add_symbol("32", SY_CONST, 0x0c7);
		add_symbol("256", SY_CONST, 0x0c8);
		add_symbol("65536", SY_CONST, 0x0c9);

		add_symbol("2048", SY_CONST, 0x0ca);
		add_symbol("0x800", SY_CONST, 0x0ca);

		add_symbol("2^28", SY_CONST, 0x0cb);
		add_symbol("0x10000000", SY_CONST, 0x0cb);

		add_symbol("2^29", SY_CONST, 0x0cc);
		add_symbol("0x20000000", SY_CONST, 0x0cc);

		add_symbol("2^30", SY_CONST, 0x0cd);
		add_symbol("0x40000000", SY_CONST, 0x0cd);

		add_symbol("2^31", SY_CONST, 0x0ce);
		add_symbol("0x80000000", SY_CONST, 0x0ce);

		add_symbol("0x7fffffff", SY_CONST, 0x0cf);

		add_symbol("0xffffffff", SY_CONST, 0x0d0);
		add_symbol("-1", SY_CONST, 0x0d0);

		add_symbol("0xfffffffe", SY_CONST, 0x0d1);
		add_symbol("-2", SY_CONST, 0x0d1);

		add_symbol("0xc0000000", SY_CONST, 0x0d2);

		add_symbol("0x4f1bbcdc", SY_CONST, 0x0d3);

		add_symbol("0x5a7ef9db", SY_CONST, 0x0d4);

		add_symbol("0x100000", SY_CONST, 0x0d5);
		add_symbol("accum", SY_ACCUM, 0x0d6);
		add_symbol("CCR", SY_CONST, 0x0d7);

		add_symbol("noise_L", SY_CONST, 0x0d8);
		add_symbol("noise_R", SY_CONST, 0x0d9);
		add_symbol("IRQREQ", SY_CONST, 0x0da);
	} else {
		/* SB Live symbols */
		add_symbol("0", SY_CONST, 0x040);
		add_symbol("1", SY_CONST, 0x041);
		add_symbol("2", SY_CONST, 0x042);
		add_symbol("3", SY_CONST, 0x043);
		add_symbol("4", SY_CONST, 0x044);
		add_symbol("8", SY_CONST, 0x045);
		add_symbol("16", SY_CONST, 0x046);
		add_symbol("32", SY_CONST, 0x047);
		add_symbol("256", SY_CONST, 0x048);
		add_symbol("65536", SY_CONST, 0x049);

		add_symbol("2^23", SY_CONST, 0x04a);
		add_symbol("0x80000", SY_CONST, 0x04a);

		add_symbol("2^28", SY_CONST, 0x04b);
		add_symbol("0x10000000", SY_CONST, 0x04b);

		add_symbol("2^29", SY_CONST, 0x04c);
		add_symbol("0x20000000", SY_CONST, 0x04c);

		add_symbol("2^30", SY_CONST, 0x04d);
		add_symbol("0x40000000", SY_CONST, 0x04d);

		add_symbol("2^31", SY_CONST, 0x04e);
		add_symbol("0x80000000", SY_CONST, 0x04e);

		add_symbol("0x7fffffff", SY_CONST, 0x04f);

		add_symbol("0xffffffff", SY_CONST, 0x050);
		add_symbol("-1", SY_CONST, 0x050);

		add_symbol("0xfffffffe", SY_CONST, 0x051);
		add_symbol("-2", SY_CONST, 0x051);

		add_symbol("accum", SY_ACCUM, 0x056);
		add_symbol("CCR", SY_CONST, 0x057);

		add_symbol("noise_L", SY_CONST, 0x058);
		add_symbol("noise_R", SY_CONST, 0x059);
		add_symbol("IRQREQ", SY_CONST, 0x05a);
	}
}

static void
produce_map(char *name)
{
	int i;
	FILE *f;

	if ((f = fopen(name, "w")) == NULL) {
		perror(name);
		return;
	}

	(void) fprintf(f, "%d\n", pc);

	for (i = 0; i < nsyms; i++) {
		(void) fprintf(f, "%04x %x %s\n",
		    symtab[i].arg, symtab[i].type, symtab[i].name);
	}

	(void) fclose(f);
	if (verbose) {
		(void) fprintf(stderr,
		    "No errors detected - Map written to %s\n", name);
	}
}

static void
produce_output(char *fname)
{
	int fd;

	if ((fd = creat(fname, 0644)) == -1) {
		perror(fname);
		exit(-1);
	}

	if (write(fd, &fle, sizeof (fle)) != sizeof (fle)) {
		perror(fname);
		exit(-1);
	}

	if (verbose) {
		(void) fprintf(stderr,
		    "No errors detected - Binary written to %s\n",
		    fname);
	}

	(void) close(fd);
}

static void
produce_header(char *fname, char *prefix)
{
	FILE *f;
	char *s;
	char sname[MAXPATHLEN + 1];
	char dname[MAXPATHLEN + 1];
	int i;
	clock_t now;
	char when[128];

	/* get basename */
	if (prefix == NULL) {
		s = strrchr(fname, '/');
		s = (s == NULL) ? fname : s + 1;
	} else {
		s = prefix;
	}
	(void) strlcpy(sname, s, sizeof (sname));

	/* strip off any extension */
	s = strchr(sname, '.');
	if (s != NULL) {
		*s = 0;
	}
	if ((f = fopen(fname, "w")) == NULL) {
		perror(fname);
		return;
	}

	if (remarks[0] != 0) {
		(void) fprintf(f, "/*\n%s */\n", remarks);
	}
	now = time(NULL);
	strftime(when, sizeof (when), "%c", localtime(&now));
	(void) fprintf(f, banner, progname, when);

	(void) strlcpy(dname, prefix ? prefix : sname, sizeof (dname));
	for (i = 0; dname[i]; i++) {
		dname[i] = toupper(dname[i]);
		if (!isalnum(dname[i])) {
			dname[i] = '_';
		}
	}

	for (i = 0; i < fle.parms.ngpr; i++) {
		(void) fprintf(f, "#define\t%s_%s\t\t%d\n",
		    dname, fle.parms.gpr[i].name, fle.parms.gpr[i].num);
	}

	(void) fprintf(f, "\n");

	if (parms_only)
		goto done;

	(void) fprintf(f, "uint32_t %s_code[] = {\n", sname);

	for (i = 0; i < pc * 2; i++) {
		if (i == 0) {
			(void) fprintf(f, "\t0x%08xU", fle.code[i]);
		} else if ((i % 4) == 0) {
			(void) fprintf(f, ",\n\t0x%08xU", fle.code[i]);
		} else {
			(void) fprintf(f, ", 0x%08xU", fle.code[i]);
		}
	}
	(void) fprintf(f, "\n};\n");

	(void) fprintf(f, "uint32_t %s_ninit = %d;\n", sname, fle.ninit);
	(void) fprintf(f, "uint32_t %s_init[] = {\n", sname);

	for (i = 0; i < fle.ninit; i++) {
		if (fle.init[i].name[0]) {
			(void) fprintf(f, "\t%u, 0x%x%s,\t/* %s */\n",
			    fle.init[i].gpr, fle.init[i].value,
			    fle.init[i].value >= 0x80000000U ? "U" : "",
			    fle.init[i].name);
		} else {
			(void) fprintf(f, "\t%u, 0x%x%s,\n",
			    fle.init[i].gpr, fle.init[i].value,
			    fle.init[i].value >= 0x80000000U ? "U" : "");
		}
	}
	(void) fprintf(f, "};\n");

done:
	(void) fclose(f);
	if (verbose) {
		(void) fprintf(stderr,
		    "No errors detected - Header written to %s\n",
		    fname);
	}
}

int
main(int argc, char *argv[])
{
	char *outfile;
	int i;
	FILE *input;
	char *tokens[10];
	int tokcnt;
	char *mapfile = NULL;
	char *header = NULL;
	char *prefix = NULL;

	outfile = NULL;
	infile = NULL;
	input = NULL;
	progname = argv[0];

	while ((i = getopt(argc, argv, "m:h:o:i:P:021v")) != EOF) {
		switch (i) {
		case 'o':
			outfile = optarg;
			break;
		case 'i':
			infile = strdup(optarg);
			break;
		case 'm':
			mapfile = optarg;
			break;
		case 'P':
			prefix = optarg;
			break;
		case 'h':
			header = optarg;
			break;
		case '0':
			parms_only = 1;
			break;
		case '2':
			is_audigy = 1;
			break;
		case '1':
			is_audigy = 0;
			break;
		case 'v':
			verbose++;
			break;
		default:
			(void) fprintf(stderr,
			    "usage: %s [-m <map>] [-h <header>] "
			    "[-o <binary>] [-i <source>] [-2|-1]",
			    progname);
			exit(-1);
			break;
		}
	}

	if ((outfile == NULL) && (mapfile == NULL) && (header == NULL)) {
		outfile = "dsp.bin";
	}

	if (infile) {
		input = fopen(infile, "r");
		if (input == NULL) {
			perror(infile);
			exit(-1);
		}
	} else {
		infile = strdup("<stdin>");
		input = stdin;
	}

	if (is_audigy) {
		gpr_base = 0x400;
		input_base = 0x40;
		output_base = 0x60;
		if (verbose)
			(void) fprintf(stderr, "Compiling for SB Audigy\n");
	} else {
		if (verbose)
			(void) fprintf(stderr, "Compiling for SB Live\n");
	}

	init_compiler();

	while ((tokcnt = getaline(input, tokens)) != -1) {
		/* skip empty lines */
		if (tokcnt == 0) {
			continue;
		}

		if (strcmp(tokens[0], "#") == 0) {
			int	num;
			if ((tokcnt >= 3) &&
			    (sscanf(tokens[1], "%d", &num) == 1)) {
				lineno = num;
				free(infile);
				infile = strdup(tokens[2]);
				/* we don't want to count the # directive */
				lineno--;
			}

			/* unknown # directive? muddle on... */
			continue;
		}
		if (*tokens[0] == '.') {
			compile_directive(tokens, tokcnt);
		} else {
			compile_asm(tokens, tokcnt);
		}
	}

	if (lineno < 1) {
		error("Empty input");
	}

	if (errors == 0) {
		if (verbose) {
			(void) fprintf(stderr,
			    "%d instructions out of 512 assembled\n", pc);
		}

		if (outfile)
			produce_output(outfile);
		if (mapfile)
			produce_map(mapfile);
		if (header)
			produce_header(header, prefix);
	}

	if (errors > 0) {
		(void) fprintf(stderr, "%d errors - compile failed\n", errors);
		exit(-1);
	}

	return (0);
}
